using System;
using System.Collections.Generic;
using System.Text;
using System.Net.Sockets;
using System.Net;
using System.Collections;
using System.Threading;

namespace ICeFramework.Utilities.Sockets
{
    public class ServerSocket : Socket
    {
        public AsyncCallback pfnWorkerCallBack;
        private ArrayList m_workerSocketList = ArrayList.Synchronized(new ArrayList());
        private int m_clientCount = 0;
        private string _secureKey = string.Empty;

        public delegate void ClientConnectDelegate(ClientConnectEventArgs e);
        public event ClientConnectDelegate ClientConnect;

        public delegate void ClientDisconnectDelegate(ClientDisconnectEventArgs e);
        public event ClientDisconnectDelegate ClientDisconnect;

        public ServerSocket(AddressFamily addressFamily, SocketType socketType, ProtocolType protocolType)
            :
            base(addressFamily, socketType, protocolType)
        {
        }

        public ServerSocket(AddressFamily addressFamily, SocketType socketType, ProtocolType protocolType, string secureKey)
            :
            base(addressFamily, socketType, protocolType)
        {
        }

        public void StartListen(int port)
        {
            IPEndPoint ipLocal = new IPEndPoint(IPAddress.Any, port);
            this.Bind(ipLocal);
            this.Listen(4);            
        }

        public void StopListen()
        {
            CloseSockets();
        }

        private void OnClientConnect(IAsyncResult asyn)
        {
            try 
            {
                Socket workerSocket = this.EndAccept(asyn);
                m_workerSocketList.Add(workerSocket);
                SocketError err;
                workerSocket.Send(Encoding.UTF8.GetBytes("Connected."), 0, Encoding.UTF8.GetByteCount("Connected."), SocketFlags.None, out err);
                

                if (ClientConnect != null)
                {
                    ClientConnect(new ClientConnectEventArgs(workerSocket));
                }

                WaitForData(workerSocket, m_clientCount);

                Interlocked.Increment(ref m_clientCount);

                this.BeginAccept(new AsyncCallback(OnClientConnect), null);
            }
            catch (ObjectDisposedException)
            {
                System.Diagnostics.Debugger.Log(0, "1", "\n OnClientConnection: Socket has been closed\n");
            }
            catch (SocketException se)
            {
                System.Diagnostics.Debugger.Log(0, "1", se.Message);
            }
        }

        public void SendData(SocketPacket packet)
        {
            if (pfnWorkerCallBack == null)
            {
                // Specify the call back function which is to be 
                // invoked when there is any write activity by the 
                // connected client
                pfnWorkerCallBack = new AsyncCallback(OnDataReceived);
            }
            
            this.BeginSend(packet.dataBuffer, 0,
                                   packet.dataBuffer.Length,
                                   SocketFlags.None,
                                   new AsyncCallback(OnDataSent),
                                   packet);

        }

        private void WaitForData(Socket soc, int clientNumber)
        {
            try
            {                
                if (pfnWorkerCallBack == null)
                {
                    // Specify the call back function which is to be 
                    // invoked when there is any write activity by the 
                    // connected client
                    pfnWorkerCallBack = new AsyncCallback(OnDataReceived);
                }
                SocketPacket theSocPkt = new SocketPacket(soc, clientNumber);
                theSocPkt.m_currentSocket = soc;
                // Start receiving any data written by the connected client
                // asynchronously
                soc.BeginReceive(theSocPkt.dataBuffer, 0,
                                   theSocPkt.dataBuffer.Length,
                                   SocketFlags.None,
                                   pfnWorkerCallBack,
                                   theSocPkt);
            }
            catch (SocketException se)
            {
                System.Diagnostics.Debugger.Log(0, "1", se.Message);
            }
        }

        public void OnDataSent(IAsyncResult asyn)
        {
            try
            {
                SocketPacket socketData = (SocketPacket)asyn.AsyncState;

                int iRx = 0;
                iRx = socketData.m_currentSocket.EndSend(asyn);
            }
            catch (ObjectDisposedException)
            {
                System.Diagnostics.Debugger.Log(0, "1", "\nOnDataSent: Socket has been closed\n");
            }
            catch (SocketException se)
            {
                System.Diagnostics.Debugger.Log(0, "1", se.Message);
            }
        }

        public void OnDataReceived(IAsyncResult asyn)
        {
            try
            {
                SocketPacket socketData = (SocketPacket)asyn.AsyncState;

                int iRx = 0;
                iRx = socketData.m_currentSocket.EndReceive(asyn);
                //char[] chars = new char[iRx + 1];
                //System.Text.Decoder d = System.Text.Encoding.UTF8.GetDecoder();
                //int charLen = d.GetChars(socketData.dataBuffer,
                //                         0, iRx, chars, 0);
                //System.String szData = new System.String(chars);
                //richTextBoxReceivedMsg.AppendText(szData);

                WaitForData(socketData.m_currentSocket, socketData.m_clientNumber);
            }
            catch (ObjectDisposedException)
            {
                System.Diagnostics.Debugger.Log(0, "1", "\nOnDataReceived: Socket has been closed\n");
            }
            catch (SocketException se)
            {
                System.Diagnostics.Debugger.Log(0, "1", se.Message);
            }
        }

        public void ClientRespond()
        { 

        }

        void CloseSockets()
        {
            this.Close();
            for (int i = 0; i < m_workerSocketList.Count; i++)
            {
                if (ClientDisconnect != null)
                {
                    ClientDisconnect(new ClientDisconnectEventArgs(((Socket)m_workerSocketList[i]), true));
                }
                ((Socket)m_workerSocketList[i]).Close();
            }
            m_workerSocketList.Clear();
        }
    }


    public class ClientConnectEventArgs
    {
        private Socket _socket;

        public Socket Socket
        {
            get { return _socket; }
        }

        public ClientConnectEventArgs(Socket socket)
        {
            _socket = socket;
        }
    }

    public class ClientDisconnectEventArgs
    {
        private Socket _socket;
        bool _normalDisconnection;

        public Socket Socket
        {
            get { return _socket; }
        }

        public bool NormalDisconnection
        {
            get { return _normalDisconnection; }
        }

        public ClientDisconnectEventArgs(Socket socket, bool normalDisconnection)
        {
            _socket = socket;
            _normalDisconnection = normalDisconnection;
        }
    }
}
